#include <my_header.h>
// 利用mmap实现文件复制时
// 使用的映射区域的大小必须是4096的整数倍
// 这是因为一个磁盘块的大小是4096字节
// 所以在映射磁盘文件数据时，最少也需要
// 映射操作一个磁盘块
#define BLOCK_SIZE (1024*1024*8)    //定义每次复制的快大小为8MB
#if 0
实现思路：
1.分别open源文件和目标文件，并计算源文件大小
2.ftruncate设置目标文件大小，避免文件大小不足，映射
失败也能够提前预留空间，避免因空间不足而复制失败。
3.记录文件复制时dest目标文件的偏移量offset，也就是已
复制字节的数量。利用该变量进行循环复制源文件到目标文件。
#endif

long long current_time_mills()
{
    struct timespec ts;
    // 获取当前时间存入ts结构体
    clock_gettime(CLOCK_REALTIME, &ts);
    // 转换为ns 获取最终秒数需要ms+ns才是完整的时间
    return (long long)ts.tv_sec * 1000000000LL + ts.tv_nsec;
}

/* Usage: ./4_mmap_cp_large_file srcfile destfile */
int main(int argc, char *argv[]){                                  
    // 记录复制前的时间
    long t1  = current_time_mills();

    ARGS_CHECK(argc, 3);

    // 打开源文件
    int src_fd = open(argv[1], O_RDONLY);
    ERROR_CHECK(src_fd, -1, "open src");
    
    // 获取源文件大小
    // off_t是一个64为有符号正数
    off_t src_size = lseek(src_fd, 0, SEEK_END);
    // lseek(src_fd, 0, SEEK_SET); lseek是操作的文件对象
    // 而mmap是直接操作修改映射的内存区域 修改或者不修改
    // lseek到文件的开头都不妨碍

#if 0
    // fstat的获取文件大小
    struct stat sb;
    // 获取文件的相关信息存入sb结构体
    int ret = fstat(src_fd, &sb);
    ERROR_CHECK(ret, -1, "fstat");
    // sb结构体可以直接获取文件大小
    off_t src_size = sb.st_size;
#endif

    // 打开目标文件
    int dest_fd = open(argv[2], O_RDWR | O_CREAT 
                       | O_TRUNC, 0666);
    ERROR_CHECK(dest_fd, -1, "open dest");

    // 为了避免目标文件大小不足映射失败
    // 且大文件复制提前占位，也能防止中途因空间不足复制失败
    int ret = ftruncate(dest_fd, src_size);
    ERROR_CHECK(ret, -1, "ftruncat");

    off_t bytes_copied= 0;  // 记录已复制的字节数
    while(bytes_copied < src_size)
    {
        // 确定本次复制的字节大小
        off_t current_block_size = (bytes_copied +
            BLOCK_SIZE > src_size) ?
    // 如果已复制字节数加上一次复制的块大小超过了源文
    // 件大小，说明这是最后一次映射复制，此时只需要映射
    // 剩余文件内容就可以了
                (src_size - bytes_copied) 
    // 不是最后一次映射复制，那就继续映射一整个块的大小
                : BLOCK_SIZE;
        
        // 映射源文件
        void *srcp = mmap(NULL, current_block_size, 
    PROT_READ, MAP_SHARED, src_fd, bytes_copied);
        ERROR_CHECK(srcp, MAP_FAILED, "mmap src");
    
        // 映射目标文件
        void *destp = mmap(NULL, current_block_size,
    PROT_READ | PROT_WRITE, MAP_SHARED, dest_fd, bytes_copied);
        ERROR_CHECK(destp, MAP_FAILED, "mmap dest");

        // 执行内存复制
        // 
        memcpy(destp, srcp, current_block_size);
    
        // 解除映射
        munmap(srcp, current_block_size);
        munmap(destp, current_block_size);

        // 更新已复制的字节数
        bytes_copied += current_block_size;
    }

    // 清理资源(文件描述符)
    close(src_fd);
    close(dest_fd);
    // 复制完成的结束时间
    long t2 = current_time_mills();
    // 打印复制所需要的时间
    printf("time = %ldns\n", (t2 -t1));
    return 0;
}


